package com.googlecode.mailnet.simulation;

import com.googlecode.mailnet.math.RandomUtil;
import com.googlecode.mailnet.info.IMessageInfo;
import com.googlecode.mailnet.info.MessageFailureType;
import com.googlecode.mailnet.info.IMessageListener;
import com.googlecode.mailnet.info.ILocation;
import com.googlecode.mailnet.netmodel.MessageFlow;

/**
 * Describes the single message constructed by some data flow. The size
 * of the message may be different and is defined by data flow during
 * instantination. Note that the size of the message and parent data
 * flow can't be changed.
 * 
 * @author Gleb Rybakov
 * @author Iskander Akishev
 */
class Message implements IMessageInfo {
	/**
	 * The size of the message (measured in number of packages).
	 */
	final int size;

	/**
	 * Moment of time when this message will be received at the outbound end of the link; depends on link delay.
	 */
	private int linkPassingTime;

	/**
	 * Position of the message within its route.
	 * <p/>
	 * Value <tt>i</tt> means that the message reside at the source of <tt>i</tt>-th link in the route or is being
	 * transferred via the <tt>i</tt>-th link.
	 */
	private int routePosition;

	/**
	 * The parent data flow instance.
	 */
	final MessageFlowState messageFlowState;
	
	final int priority;

	Message(int size, MessageFlowState messageFlowState, int priority) {
		this.messageFlowState = messageFlowState;
		this.size = size;
		this.priority = priority;
		routePosition = 0;
	}

	/**
	 * Drops the message. It is considered that it's delivery was failed and the message will be resent again
	 * from the source point at the next simulation step.
	 * <p/>
	 * WARNING: don't forget to remove this message from all internal queues before calling this method.
     * @param failureType failure type
     * @param location failure location
     */
	void drop(MessageFailureType failureType, ILocation location) {
        for (IMessageListener messageListener : messageFlowState.simulationState.messageListeners) {
            messageListener.onMessageFailed(this, failureType, location);
        }
		messageFlowState.undeliveredMessages.add(this);
		routePosition = 0;
	}

	/**
	 * Simulates message corruption.
	 * @param singlePackageCorruptionProbability probability of single package corruption
	 * @return randomly returns <tt>true</tt> with probability, equal to the probability of this message being
	 * <b>corrupted</b> during transmission.
	 */
	boolean simulateProbableCorruption(double singlePackageCorruptionProbability) {
		double successProbability = Math.pow(1d - singlePackageCorruptionProbability, size);
		return RandomUtil.nextBoolean(1d - successProbability);
	}

	boolean isStillTransferring() {
		return linkPassingTime > messageFlowState.simulationState.currentStepNumber;
	}

	void arriveAt(NodeState destination) {
		assert (!isStillTransferring());
		assert linkPassingTime > 0;
		linkPassingTime = 0; // ...just in case
		destination.currentInternalMessages.add(this);
		routePosition++;
	}

	boolean reachedFinalDestination() {
		return routePosition == messageFlowState.messageFlow.getRoute().getLinksCount();
	}

	void sendThrough(LinkState linkState) {
		linkPassingTime = messageFlowState.simulationState.currentStepNumber + linkState.link.getDelay();
		linkState.transmittingMessages.add(this);
		for (IMessageListener listener : messageFlowState.simulationState.messageListeners) {
			listener.onMessageTransmitted(this, linkState.sourceState);
		}
	}

	LinkState getAheadLink() {
		return messageFlowState.simulationState.linkStates.get(
			messageFlowState.messageFlow.getRoute().getLink(routePosition)
		);
	}

    public int size() {
        return size;
    }

    public int priority() {
        return priority;
    }

    public MessageFlow getMessageFlow() {
        return messageFlowState.messageFlow;
    }
}
